import Factory from "../core/factory";
import Layers from "../layers/index";
import http from "../http";

/**
 * 数据字典明细
 */
interface DictionaryItem<T = string | number | boolean> {
    /**
     * 名字
     */
    value: T;
    /**
     * 标签
     */
    label: string;
    /**
     * 样式
     */
    style?: string;
    /**
     * 原始数据
     *
     * 有些数据字典，可能是通过某些文字列表，格式转换之后获得，
     * 这个字段用于存储格式化之前的数据。
     */
    ref?: any;
}

/**
 * 数据字典
 */
interface DictionaryType<T = string | number | boolean> {
    /**
     * 数据字典元素
     *
     * @private
     */
    list: DictionaryItem<T>[];

    /**
     * 默认的样式 class-name
     * @private
     */
    defaultStyle: string;
}

/**
 * 数据字典查询
 *
 * @param key - 数据字典唯一 ID
 * @returns {Promise<Dictionary>} 数据字典
 */
type DictionaryQuery = (key: string) => Promise<Dictionary>;

/**
 * 默认的数据字典查询
 *
 * 如果需要切换到其它系统，最简单的方式就是直接改动当前函数
 */
const provider: DictionaryQuery = (key: string) => {
    let arr = key.split('.');
    return http.ajax({
        url: 'sys/dic/config/info',
        method: 'GET',
        params: {tableName: arr[0], columnName: arr[1]}
    }).then(ret => {
        return new Dictionary(ret.list, ret.defaultStyle);
    })
}


/**
 * 数据字典缓存
 *
 * 一次会话中，不重复查询字典
 */
const cache = new Factory<Dictionary>()

/**
 * 前端直接定义的字典
 *
 * 如果 provider 中可以获取到同名字典，则当前字典会被逐渐清除
 */
const defined = new Factory<Dictionary>();

export {DictionaryItem, DictionaryType, cache, defined};

/**
 * 数据字典
 *
 * 内部封装了数据字典相关的逻辑
 *
 * 想分离工厂类与字典之间的耦合，但是暂时没有特别好的思路
 */
class Dictionary<T = string | number | boolean> implements DictionaryType {
    /**
     * 数据字典元素
     *
     * @private
     */
    public list: DictionaryItem<T>[];

    /**
     * 默认的样式 class-name
     *
     * @private
     */
    public defaultStyle: string;

    /**
     * 数据字典
     *
     * @param list - 字典列表（格式化好的数据字典）
     * @param style - 默认样式
     */
    constructor(list: DictionaryItem<T>[] = [], style: string = '') {
        this.list = list;
        this.defaultStyle = style;
    }

    /**
     * 将自身添加到缓存
     *
     * @param key {string}
     */
    registerAs(key: string): void {
        Dictionary.register(key, this);
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @param compare - 比较函数
     * @returns {DictionaryItem | undefined} 数据字典
     */
    find(value: T, compare: (a: T, b: T) => boolean): DictionaryItem<T> | undefined {
        const options = this.list;
        if (options != null) {
            for (let i = 0; i < options.length; i++) {
                if (compare(options[i].value, value)) {
                    return options[i];
                }
            }
        }
    }


    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @returns 数据字典
     */
    findByValue(value: T): DictionaryItem<T> | undefined {
        const options = this.list;
        if (options != null) {
            for (let i = 0; i < options.length; i++) {
                if (options[i].value === value) {
                    return options[i];
                }
            }
        }
    }

    /**
     * 按照区间进行数据字典匹配
     *
     * @param value - 值
     * @returns 数据字典
     */
    findByRange(value: number): DictionaryItem<T> | undefined {
        return this.find(value, Layers.isInRange);
    }

    /**
     * 获取匹配的数据字典
     *
     * @param value - 值
     * @param label - 默认标签
     * @param style - 默认样式
     * @returns 数据字典的拷贝
     */
    matchByValue(value: T, label?: string, style?: string): DictionaryItem<T> {
        const item = this.findByValue(value);
        if (item !== undefined) {
            return {label: item.label, style: item.style, value: value, ref: item.ref};
        } else {
            // 意外的场景，未找到数据字典
            console.log(this.list);
            console.log('dict not found, use default value!');

            const l = label ? label : value;
            const s = style ? style : this.defaultStyle;
            return {label: l, style: s, value: value};
        }
    }


    /**
     * 获取匹配的数据字典
     *
     * 使用数学区间比较的特殊数学字典
     *
     * @param value - 值
     * @param label - 默认标签
     * @param style - 默认样式
     * @returns 数据字典的拷贝
     */
    matchByRange(value: number, label?: string, style?: string): DictionaryItem<T> {
        const item = this.findByRange(value);
        if (item !== undefined) {
            return {label: item.label, style: item.style, value: value, ref: item.ref};
        } else {
            const l = label ? label : value;
            const s = style ? style : this.defaultStyle;
            return {label: l, style: s, value: value};
        }
    }


    /**
     * 获取匹配的数据字典标签
     *
     * @param value - 值
     * @param def  - 默认值
     * @returns  - 数据字典标签
     */
    getLabelByValue(value: T, def: string = undefined): string {
        let dic = this.findByValue(value);
        if (dic != null) {
            return dic.label;
        } else {
            return def ? def : value;
        }
    }

    // ------------------------------------- static start ---------------------------------------

    /**
     * 数据字典哑元元素
     *
     * 空的数据字典，用语占位，避免空值问题
     */
    static readonly DEF: Dictionary = new Dictionary();


    /**
     * 获取数据字典
     *
     * 多数数据字典用的是这个数据字典加载器
     *
     * 如果未获取到数据字典，则返回 undefined，因为是异步载入，因此必须注意页面渲染的生命周期
     *
     * @param key {string}             字典名称
     * @returns {Promise<Dictionary>}  数据字典
     */
    static query(key: string): Promise<Dictionary> {
        // 如果缓存中存在，直接返回
        const cached = cache.get(key);
        if (cached !== undefined) return Promise.resolve(cached);

        return provider(key).then(ret => {
            if (ret !== undefined) {
                // cache the dictionary
                cache.put(key, ret);

                // defined in other ways, clear defined-dictionary
                // 如果字典存在其它来源，清除前端直接定义的字典
                defined.remove(key);

                // resolve
                return ret;
            } else {
                // using defined-cache
                const defined_ = defined.get(key);
                return defined_ !== undefined ? defined_ : Dictionary.DEF;
            }
        }).catch(e => {
            console.error(`query dictionary failed: ${key}`, e);
            return Dictionary.DEF;
        });
    }

    /**
     * 将数组格式化为数据字典
     *
     * @param list  - 任意数组
     * @param label - 字段名
     * @param value - 字段值
     */
    static from(list: any[], label: string, value: string): Dictionary {
        const arr: DictionaryItem[] = Layers.wrapArray(list, (ele): DictionaryItem => {
            return {label: ele[label], value: ele[value], ref: ele}
        });

        return new Dictionary(arr);
    }
}


export default Dictionary;